#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

// 搭建一个Udpserver，需要对应的套接字，以及填充对应的socketAddr结构体内部的数据
// 然后在启动的时候进入死循环，使用recefrom函数来接收客户端发送的数据。

// 为了让业务和服务器底层的网络交流解耦，我们用一个函数类型来封装一下，然后再回调它。

namespace Server
{
	using namespace std;
	// 默认IP地址
	const static string defaultIP = "0.0.0.0";
	enum
	{
		USAGE_ERR = 1,
		SOCKET_ERR, 
		BIND_ERR,
		OPEN_ERR,
		CUR_ERR
	};

	// 定义一个函数类型
	typedef function<void(int, string, uint16_t, string)> fun_t;

	class UDPServer
	{
	public:
		UDPServer(fun_t method, const uint16_t &port, const string &IP = defaultIP)
			: _callback(method), _IP(IP), _port(port)
		{
		}

		void init()
		{
			// 初始化Scoket，指定对应的网络通信类型，然后获取返回的文件FD
			// int socket(int domain, int type, int protocol);

			_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
			if (_socket_fd == -1)
			{
				std::cerr << "Server bulid socket failed!" << strerror(errno) << endl;
				exit(SOCKET_ERR);
			}
			std ::cout << "socket success: "
					   << " : " << _socket_fd << endl;
			// 指定完Socket内容之后开始填充Socketaddr结构体内部的数据段,准备绑定

			struct sockaddr_in local;
			bzero(&local, sizeof(local));
			// int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

			// 第一步：指定协议家族类型
			local.sin_family = AF_INET;

			// 第二步：填充端口号
			// 端口号需要转换大小端字节序才能正常传输到网络中
			local.sin_port = htons(_port);

			// 第三步：填充IP地址 ,填充地址不仅需要转换大端字节序，还需要转换成整数风格
			// 调用inet_addr (const char* ch)即可直接转换
			// local.sin_addr.s_addr= inet_addr(_IP.c_str());
			// local.sin_addr.s_addr = inet_addr(_IP.c_str());
			local.sin_addr.s_addr = htonl(INADDR_ANY);
			// 最后一步:调用绑定bind函数int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
			int bret = bind(_socket_fd, (struct sockaddr *)&local, sizeof(local));
			if (bret == -1)
			{
				std::cerr << "bind failed!" << strerror(errno) << endl;
				exit(BIND_ERR);
			}
		}

		void start()
		{
			// ssize_t recvfrom(int socket, void *restrict buffer, size_t length,int flags, struct sockaddr *restrict address,
			//   socklen_t *restrict address_len);

			char buffer[1024];
			std ::cout << "服务器已启动!" << endl;
			std ::cout << "获取的端口号:" << _port << endl;

			while (true)
			{
				// recvfrom后面的两个参数是输出型参数,标明了客户端的sockaddr的数据结构,所以我们需要一个对应的变量来获取它
				struct sockaddr_in client;
				socklen_t clilen = sizeof(client);

				ssize_t n = recvfrom(_socket_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client, &clilen);
				if (n > 0)
				{

					buffer[n] = 0;
					// 客户端的IP
					string clientIP = inet_ntoa(client.sin_addr);
					// 客户端的端口号
					uint16_t clientport = ntohs(client.sin_port);
					string message = buffer;

					std ::cout << clientIP << "[" << clientport << "]# " << message << endl;


					// typedef function<void(int,string,uint16_t,string)> fun_t;
					// 此处处理回调业务函数

					// 为了给客户端发回去数据，要交付对应的客户端的IP地址和端口
					_callback(_socket_fd, clientIP,clientport,message);
				}
			}
		}

		~UDPServer()
		{
		}

	private:
		// 作为一个服务器，需要传入对应的端口号以及对应的IP，我们不需要显示的指定IP，而是直接指定对应的端口。
		// 端口号是一个uint16_t类型的变量
		uint16_t _port;
		string _IP;
		int _socket_fd;

		fun_t _callback;
	};

}